/**
  ******************************************************************************
  * @author     Chris
  * @since      2024/6/21 19:20
  *
  * @file       pd_i2c_sim.c
  * @brief      IIC simulate driver.
  *
  * @note       This file contains the simulate driver for the IIC.
  *
  * @warning    None.
  ******************************************************************************
  * Change Logs:
  *   Date          Author       Notes
  *   2024/6/21     Chris        the first version
  *
  ******************************************************************************
  */


#include <air001xx_ll_gpio.h>
#include <delay.h>
#include "pd_i2c_sim.h"

static void I2CSimulate_writeBit(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, FlagStatus BitVal);

#define SCL(x)      I2CSimulate_writeBit(this->i2c.sck->GPIOx, this->i2c.sck->Pin, x)
#define SDA(x)      I2CSimulate_writeBit(this->i2c.sda->GPIOx, this->i2c.sda->Pin, x)
#define SDA_OUT()   this->i2c.sda->setOutMode(this->i2c.sda)
#define SDA_IN()    this->i2c.sda->setInMode(this->i2c.sda)
#define SDA_GET()   this->i2c.sda->getBit(this->i2c.sda)

static void I2CSimulate_writeBit(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, FlagStatus BitVal) {
    delay_us(1);
    GPIOx->BSRR = BitVal ? GPIO_Pin : GPIO_Pin << 16;
}

static void I2CSimulate_start(I2CSimulate *this) {
    SDA_OUT();

    SCL(1);
    SDA(0);
    SDA(1);
    SDA(0);
    SCL(0);
}

static void I2CSimulate_stop(I2CSimulate *this) {
    SDA_OUT();

    SCL(0);
    SDA(0);
    SCL(1);
    SDA(1);
}

static void I2CSimulate_sendAck(I2CSimulate *this, unsigned char ack) {
    SDA_OUT();

    SCL(0);
    SDA(0);

    if (!ack)
        SDA(0);
    else
        SDA(1);

    SCL(1);
    SCL(0);
    SDA(1);
}

static uint8_t I2CSimulate_waitAck(I2CSimulate *this) {
    char ack = 0;
    unsigned char ack_flag = 10;

    SCL(0);
    SDA(1);
    SDA_IN();

    SCL(1);
    while ((SDA_GET() == 1) && (ack_flag)) {
        ack_flag--;
    }

    if (ack_flag <= 0) {
        I2CSimulate_stop(this);
        return 1;
    } else {
        SCL(0);
        SDA_OUT();
    }
    return ack;
}

static void I2CSimulate_sendByte(I2CSimulate *this, uint8_t byte) {
    SDA_OUT();
    SCL(0);

    for (uint8_t i = 0; i < 8; i++) {
        SDA((byte & 0x80) >> 7);
        SCL(1);
        SCL(0);
        byte <<= 1;
    }
}

static uint8_t I2CSimulate_readByte(I2CSimulate *this) {
    unsigned char receive = 0;

    SDA_IN();
    for (int i = 0; i < 8; i++) {
        SCL(0);
        SCL(1);
        receive <<= 1;
        if (SDA_GET()) {
            receive |= 1;
        }
    }
    SCL(0);
    return receive;
}

static void I2CSimulate_init(IIC *super) {
    I2CSimulate *this = (I2CSimulate *) super;
    this->i2c.sck->init(this->i2c.sck);
    this->i2c.sda->init(this->i2c.sda);

    this->i2c.sck->setBit(this->i2c.sck);
    this->i2c.sda->setBit(this->i2c.sda);
}

static uint8_t I2CSimulate_send(IIC *super,
                                uint8_t salveAddress, uint16_t regAddress, uint8_t *dat, uint8_t len) {
    I2CSimulate *this = (I2CSimulate *) super;

    // IIC start and send slave address
    I2CSimulate_start(this);
    I2CSimulate_sendByte(this, (salveAddress << 1) | 0);
    if (I2CSimulate_waitAck(this) == 1) {
        I2CSimulate_stop(this);
        return 1;
    }

    // IIC send register address that wait to write
    if ((regAddress & 0xFF00) == 0) {
        I2CSimulate_sendByte(this, regAddress);
    } else {
        I2CSimulate_sendByte(this, regAddress >> 8);
        delay_us(1);
        I2CSimulate_sendByte(this, regAddress);
    }
    if (I2CSimulate_waitAck(this) == 1) {
        I2CSimulate_stop(this);
        return 2;
    }

    // IIC send data
    for (int i = 0; i < len; ++i) {
        I2CSimulate_sendByte(this, dat[i]);
        if (I2CSimulate_waitAck(this) == 1) {
            I2CSimulate_stop(this);
            return 3 + i;
        }
    }

    // IIC stop
    I2CSimulate_stop(this);

    return 0;
}

static uint8_t I2CSimulate_read(IIC *super,
                                uint8_t salveAddress, uint16_t regAddress, uint8_t *dat, uint8_t len) {
    I2CSimulate *this = (I2CSimulate *) super;

    uint8_t i;

    // IIC start and send slave address
    I2CSimulate_start(this);
    I2CSimulate_sendByte(this, (salveAddress << 1) | 0);
    if (I2CSimulate_waitAck(this) == 1) {
        I2CSimulate_stop(this);
        return 1;
    }

    // IIC send register address that wait to read
    if ((regAddress & 0xFF00) == 0) {
        I2CSimulate_sendByte(this, regAddress);
    } else {
        I2CSimulate_sendByte(this, regAddress >> 8);
        delay_us(1);
        I2CSimulate_sendByte(this, regAddress);
    }
    if (I2CSimulate_waitAck(this) == 1) {
        I2CSimulate_stop(this);
        return 2;
    }

    // IIC start and send slave address
    I2CSimulate_start(this);
    I2CSimulate_sendByte(this, (salveAddress << 1) | 1);
    if (I2CSimulate_waitAck(this) == 1) {
        I2CSimulate_stop(this);
        return 3;
    }

    // IIC read (len-1) byte, and send ack(0) after every times
    for (i = 0; i < len - 1; ++i) {
        dat[i] = I2CSimulate_readByte(this);
        I2CSimulate_sendAck(this, 0);
    }
    // IIC read the last byte, and send ack(1)
    dat[i] = I2CSimulate_readByte(this);
    I2CSimulate_sendAck(this, 1);
    // IIC stop
    I2CSimulate_stop(this);

    return 0;
}

static I2CSimulate *build(GPIO *sck, GPIO *sda) {
    sck->Speed = LL_GPIO_SPEED_FREQ_HIGH;
    sda->Speed = LL_GPIO_SPEED_FREQ_HIGH;

    I2CSimulate *i2cSimulate = malloc(sizeof(I2CSimulate));

    Driver_add(i2cSimulate, GEN_PERM);

    IIC *i2c = I2CBuilder.build(sck, sda);
    i2cSimulate->i2c      = *i2c;
    i2cSimulate->i2c.init = &I2CSimulate_init;
    i2cSimulate->i2c.send = &I2CSimulate_send;
    i2cSimulate->i2c.read = &I2CSimulate_read;

    return i2cSimulate;
}

const struct I2CSimulateClass I2CSimulateBuilder = {.build = &build};
